#include "StdAfx.h"
#include "AnsiInterpreter.h"
#include "AsciiConstants.h"
#include "AnsiCommand.h"

map<char, CAnsiCommand::Action> CAnsiInterpreter::letterToActionMapping;

CAnsiInterpreter::CAnsiInterpreter(void)
{
	InitializeLetterToActionMapping();

	this->state = Text;

	this->textBuffer = new unsigned char[CAnsiInterpreter::defaultBufferSize];
	this->textLength = 0;
	
	this->parameterBuffer = new int[CAnsiInterpreter::maxParameterLength];
	this->parameterLength = 0;

	PrepareToReadNextParam();
}

CAnsiInterpreter::~CAnsiInterpreter(void)
{
	delete[] this->textBuffer;
	delete[] this->parameterBuffer;
}

void CAnsiInterpreter::ProcessInput(unsigned char* value, int count)
{
	for (int i = 0; i < count; i ++)
		ProcessByte(value[i]);

	FlushOutText();
}

void CAnsiInterpreter::ProcessByte(unsigned char value)
{
	switch (this->state)
    {
	case Text:
		if (value == AsciiByte::ESC)
		{
			this->state = WaitingForLBR;
			FlushOutText();
		}
		else
		{
			this->textBuffer[this->textLength++] = value;
			if (this->textLength == defaultBufferSize)
				FlushOutText();
		}
		break;
	
	case WaitingForLBR:
        if (value == AsciiByte::LBR)
			this->state = Escaped;
        else
			ASSERT(FALSE); // Invalid escape sequence, ESC not followed by [
		break;
                
	case Escaped:
		if (isalpha(value))
        {
			if (!this->parameterOmitted)
                this->parameterBuffer[this->parameterLength++] = this->parameterHolder;
                        
			FireAnsiCommand(value);
			PrepareToReadNextParam();
			this->parameterLength = 0;
			this->state = Text;
                    
		}
		else if (value == AsciiByte::SEMICOLON)
		{
			this->parameterBuffer[this->parameterLength++] = this->parameterOmitted ? -1 : this->parameterHolder;
			PrepareToReadNextParam();
		}
		else if (isdigit(value))
        {
			this->parameterHolder = this->parameterHolder * 10 + (value - '0');
            this->parameterOmitted = false;
		}
		else
			ASSERT(FALSE); // Invalid escape sequence
		break;
	}
}

void CAnsiInterpreter::FlushOutText()
{
    if (this->textLength == 0)
        return;

    // If the last byte is half of a DBCS, leave it in the buffer for next flush
	// TODO: can we move this to the parsing part? Saves an interation
    bool half = false;

    for (int i = 0; i < this->textLength; i++)
        half = !half && !(__isascii(this->textBuffer[i]));

    if (this->textLength == 1 && half)
        return;

	int len = half ? this->textLength - 1 : this->textLength;
    unsigned char* payload = new unsigned char[len];
	memcpy(payload, this->textBuffer, len);

    if (half)
    {
        this->textBuffer[0] = this->textBuffer[this->textLength - 1];
        this->textLength = 1;
    }
    else
        this->textLength = 0;

	OnTextReceived(payload, len);
}

void CAnsiInterpreter::FireAnsiCommand(unsigned char b)
{
	map<char, CAnsiCommand::Action>::iterator iterator = letterToActionMapping.find(b);
	if (iterator != letterToActionMapping.end())
	{
		CAnsiCommand* command = new CAnsiCommand (iterator->second, this->parameterBuffer, this->parameterLength);
		OnAnsiCommandReceived(command);
	}
	else
		ASSERT(FALSE); // Unrecognized or unsupported ANSI escape
}

void CAnsiInterpreter::PrepareToReadNextParam()
{
    this->parameterHolder = 0;
    this->parameterOmitted = true;
}

void CAnsiInterpreter::InitializeLetterToActionMapping()
{
	// This is not thread safe. But I suppose it will do no harm to initialize more than once.
	static bool initialized = false;

	if (!initialized)
	{
		letterToActionMapping['A'] = CAnsiCommand::CursorUp;
		letterToActionMapping['B'] = CAnsiCommand::CursorDown;
		letterToActionMapping['C'] = CAnsiCommand::CursorForward;
		letterToActionMapping['D'] = CAnsiCommand::CursorBack;
		letterToActionMapping['E'] = CAnsiCommand::CursorNextLine;
		letterToActionMapping['F'] = CAnsiCommand::CursorPrevLine;
		letterToActionMapping['G'] = CAnsiCommand::CursorHorizontal;
		letterToActionMapping['H'] = CAnsiCommand::CursorPosition;
		letterToActionMapping['J'] = CAnsiCommand::EraseInDisplay;
		letterToActionMapping['K'] = CAnsiCommand::EraseInLine;
		letterToActionMapping['L'] = CAnsiCommand::InsertLine;
		letterToActionMapping['m'] = CAnsiCommand::SelectGraphicRendition;
		letterToActionMapping['n'] = CAnsiCommand::DeviceStatusReport;
		letterToActionMapping['s'] = CAnsiCommand::SaveCursorPosition;
		letterToActionMapping['u'] = CAnsiCommand::RestoreCursorPosition;

		initialized = true;
	}
}